/**
 * A Drawing represents the document our application operates on.
 * It consists of zero or more Shapes.
 * A Drawing is observable by DrawingListeners.
 * 
 * @author Giuseppe Mendola (giuseppe.mendola@usi.ch); Gregory Banfi (gregory.banfi@usi.ch)
 */

package ch.usi.inf.pf2.gui;

import java.util.ArrayList;
import java.util.Iterator;
import ch.usi.inf.pf2.Component;
import ch.usi.inf.pf2.Value;
import ch.usi.inf.pf2.Wire;
import ch.usi.inf.pf2.Gates.Gate;
import ch.usi.inf.pf2.pins.OutputPin;
import ch.usi.inf.pf2.puts.Input;
import ch.usi.inf.pf2.puts.Output;

public class Drawing implements Iterable<Component> {

	private static ArrayList<Component> components;
	private ArrayList<DrawingListener> drawinglisteners;
	private static ArrayList<Gate> gates;
	private static ArrayList<Input> inputs;
	private static ArrayList<ArrayListInput> multipleInputs;
	private static ArrayList<Integer> outputList;
	private static boolean multipleInput;

	public Drawing() {
		multipleInput = false;
		components = new ArrayList<Component>();
		gates = new ArrayList<Gate>();
		inputs = new ArrayList<Input>();
		drawinglisteners = new ArrayList<DrawingListener>();
		multipleInputs = new ArrayList<ArrayListInput>();
		outputList=new ArrayList<Integer>();
	}

	public static void run() {
		int step = 0;
		boolean stop = false;
		ArrayList<Component> components = new ArrayList<Component>();
		Value outputValue=null;
		outputList.clear();
		if (multipleInput == true) {
			while (!stop) {
				for (ArrayListInput arrayInput : multipleInputs) {
					if (step < arrayInput.getInputLength()) {
						arrayInput.getInput().setValue(
								arrayInput.getValues().get(step));
						arrayInput.getInput().getOutputPin()
								.setValue(arrayInput.getValues().get(step));
						for (Wire wire : arrayInput.getInput()
								.getOutConnectedWires()) {
							wire.run();
							components.add(wire.getConnectedComponent());
						}

					} else {
						stop = true;
					}
				}
				for (Component component : components) {
					while (!(component instanceof Output)) {
						component.compute(component.getInputPins());
						for (Wire wire2 : component.getOutConnectedWires()) {
							wire2.run();
							component = wire2.getConnectedComponent();
							if (component instanceof Output) {
								component.setOutputValue();
							    outputValue=component.getValue();
								Model.getDrawing().fireDrawingChanged();

							}
						}

					}
				}
				step++;
				components.clear();
				if (!stop){
				System.out.println(outputValue);
				
				if (outputValue.equals(Value.TRUE)){
					outputList.add(1);
				}
				else if (outputValue.equals(Value.FALSE)){
					outputList.add(0);
				}
				}
				
			else{Waveform.drawWaveform(outputList);}
				Model.getDrawing().fireDrawingChanged();
			}
			
		}
	}

	public static void changeValue1() {
		for (Input input : inputs) {
			if (input.getValue() == Value.FALSE || input.getValue() == Value.UNKNOWN) {
				input.setValue(Value.TRUE);
				input.setMultipleInput(false);
				Model.getDrawing().fireDrawingChanged();
				input.getOutputPin().setValue(Value.TRUE);
				Model.getDrawing().fireDrawingChanged();
				for (Wire wire : input.getOutConnectedWires()) {
					wire.run();
					Component component = wire.getConnectedComponent();
					Model.getDrawing().fireDrawingChanged();
					while (!(component instanceof Output)) {
						component.compute(component.getInputPins());
						System.out.println(component.getOutputPin().getValue());
						Model.getDrawing().fireDrawingChanged();
						for (Wire wire2 : component.getOutConnectedWires()) {
							wire2.run();
							component = wire2.getConnectedComponent();
							Model.getDrawing().fireDrawingChanged();
							if (component instanceof Output) {
								component.setOutputValue();
								System.out.println(component);
								Model.getDrawing().fireDrawingChanged();
							} else {
								System.out.println(component);
							}
						}

					}
				}
			}
		}
	}

	public static void changeValue0() {
		for (Input input : inputs) {
			if (input.getValue() == Value.TRUE || input.getValue() == Value.UNKNOWN) {
				input.setValue(Value.FALSE);
				input.getOutputPin().setValue(Value.FALSE);
				Model.getDrawing().fireDrawingChanged();
				for (Wire wire : input.getOutConnectedWires()) {
					wire.run();
					Component component = wire.getConnectedComponent();
					Model.getDrawing().fireDrawingChanged();
					while (!(component instanceof Output)) {
						component.compute(component.getInputPins());
						System.out.println(component.getOutputPin().getValue());
						Model.getDrawing().fireDrawingChanged();
						for (Wire wire2 : component.getOutConnectedWires()) {
							wire2.run();
							component = wire2.getConnectedComponent();
							Model.getDrawing().fireDrawingChanged();
							if (component instanceof Output) {
								component.setOutputValue();
								System.out.println(component);
								System.out.println("outvalue is  "
										+ component.getValue());
								Model.getDrawing().fireDrawingChanged();
							} else {
								System.out.println(component);
								System.out.println(component.getValue());
							}
						}

					}
				}
			}
		}
	}

	/**
	 * This method returns the size of the ArrayList<Component> components.
	 * 
	 * @return components.size() The size of the list of components.
	 */
	public int size() {
		return components.size();
	}

	/**
	 * This method returns the gates field
	 * 
	 * @return gates The gates field
	 */
	public static ArrayList<Gate> getGates() {
		return gates;
	}

	/**
	 * This method returns the inputs field
	 * 
	 * @return inputs The inputs field
	 */
	public static ArrayList<Input> getInputs() {
		return inputs;
	}

	/**
	 * This method adds a component to the ArrayList<Component> components. And
	 * advises all the listeners that the drawing is changed.
	 * 
	 * @param component
	 *            The component to add.
	 */
	public void add(final ArrayListInput multipleInput) {
		multipleInputs.add(multipleInput);
		fireDrawingChanged();
	}

	public void add(final Component component) {
		components.add(component);
		fireDrawingChanged();
	}

	public static ArrayList<Component> getComponents() {
		return components;
	}

	/**
	 * This method removes a component to the ArrayList<Component> components.
	 * And advises all the listeners that the drawing is changed.
	 * 
	 * @param component
	 *            The component to remove.
	 */
	public void remove(final Component component) {
		components.remove(component);
		fireDrawingChanged();
	}

	/**
	 * This method returns a component if there is one on the coordinates
	 * indicates by x and y, passed as argument.
	 * 
	 * @param x
	 *            The x coordinate.
	 * @param y
	 *            The y coordinate.
	 * @return component Either it returns the component if it has found it or
	 *         null if it doesn't find anything.
	 */
	public Component findComponentAt(final int x, final int y) {
		// System.out.println("findShapeAt("+x+","+y+")");
		for (final Component component : components) {
			// System.out.println("  shape: "+shape);
			if (component.contains(x, y)) {
				// System.out.println("  found!");
				return component;
			}
		}
		return null;
	}

	/**
	 * This method constructs a iterator into itself and returns it.
	 * 
	 * @return Iterator<Component> An iterator for Component.
	 */
	@Override
	public Iterator<Component> iterator() {
		return new Iterator<Component>() {
			private int position = 0;

			@Override
			public boolean hasNext() {
				return position < components.size();
			}

			@Override
			public Component next() {
				return components.get(position++);
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}

	// --- listener management
	public void addDrawingListener(final DrawingListener li) {
		drawinglisteners.add(li);
	}

	/**
	 * This method advises all the listeners that the drawing is changed.
	 */
	private void fireDrawingChanged() {
		for (final DrawingListener li : drawinglisteners) {
			li.drawingChanged();
		}
	}

	public void setMultipleInput(boolean multipleInput) {
		this.multipleInput = multipleInput;
	}

	public static ArrayList<ArrayListInput> getMultipleInputs() {
		return multipleInputs;
	}

	public static boolean isMultipleInput() {
		return multipleInput;
	}

}
